#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
#include <iostream>
#include <stack>
#include <algorithm>
#include <map>
#include <cassert>
#include <sstream>

#include "../include/client.h"
//#include "my_tokenize.h"
//#include "split_csv.h"
#include "global_def.h"

//#define MY_DEBUG_A 


std::vector<IndexDbS *> __idbs_all;
std::vector<IndexDbI *> __idbi_all;

#include "MyDb.hpp"
#include "smart_cursor.h"


using namespace std;

MyDb *id2db[MAX_NAME_HASH];
#include "my_table.hpp"

#include "tree_node.h"

void my_split_csv(const char *buf, vector<string>& token)
{
	char temp[65536];
	int i, j;

	token.clear();
	j = 0;

	for (i = 0; buf[i]; i++) {
		if (buf[i] == ',') {
			temp[j] = '\0';
			token.push_back(temp);
			j = 0;
		} else {
			temp[j++] = buf[i];
		}
	}

	temp[j] = '\0';
	token.push_back(temp);
}


void my_tokenize(const char *buf, vector<string>& token)
{
	char temp[65536];
	int i, j;

	token.clear();

	for (i = 0; buf[i]; i++) {
		// Space
		if (buf[i] == ' ' ||
			buf[i] == '\t' ||
			buf[i] == '\r' ||
			buf[i] == '\n')
			continue;

		// Identifier
		if (buf[i] == '_' ||
			'a' <= buf[i] && buf[i] <= 'z' ||
			'A' <= buf[i] && buf[i] <= 'Z') {
			j = 0;
			while (buf[i] == '_' ||
				'a' <= buf[i] && buf[i] <= 'z' ||
				'A' <= buf[i] && buf[i] <= 'Z' ||
				'0' <= buf[i] && buf[i] <= '9') {
				temp[j++] = buf[i++];
			}
			temp[j] = '\0';
			token.push_back(temp);
			i--;
			continue;
		}

		// Number
		if ('0' <= buf[i] && buf[i] <= '9') {
			j = 0;
			while ('0' <= buf[i] && buf[i] <= '9') {
				temp[j++] = buf[i++];
			}
			temp[j] = '\0';
			token.push_back(temp);
			i--;
			continue;
		}

		// String
		if (buf[i] == '\'') {
			j = 0;
			while (1) {
				temp[j++] = buf[i++];
				if (j > 1 && temp[j - 1] == '\'')
					break;
			}
			temp[j] = '\0';
			token.push_back(temp);
			i--;
			continue;
		}

		// < = > , ;
		if (buf[i] == '=' || buf[i] == '<' || buf[i] == '>' ||
			buf[i] == ',' || buf[i] == ';') {
			temp[0] = buf[i];
			temp[1] = '\0';
			token.push_back(temp);
			continue;
		}

		//added by chenyh
		if (buf[i] == '(' || buf[i] == ')' ){
			temp[0] = buf[i];
			temp[1] = '\0';
			token.push_back(temp);
			continue;
		}

		fprintf(stderr, "ERROR: This code should never be executed.\n");
		exit(1);
	}
}




//vector < string > result;
//unordered_map < OBJID, MyDb * >id2db;

void
parseSelect(const string & sql,
	    vector < OBJID > &outputCol,
	    vector < CondInfo > &cond, vector < OBJID > &table);

CondInfo
parseCondition(const string & op1, const string & opc, const string & op2);


void
create(const string & table_name, const vector < string > &column_name,
       const vector < string > &column_type,
       const vector < string > &primary_key)
{

    // info.dump (cout);
    MyDb *db = new MyDb(/*getDbEnv(),*/ table_name, column_name, column_type,primary_key);
    id2db[db->tableId] = db;
}

void insertRowFromCSV(MyDb * db, const vector<string> & row)
{
    //vector < string > items;
    //my_split_csv(row.c_str(), items);
    db->insertRow(row);
}

void load(const string & table_name, const vector < string > &row)
{

    //cout << "@@@@@@@@ LOAD" << endl;
    //cerr << "load " << table_name << endl;
    OBJID tid = IdManager::getInstance()->getIdByName(table_name);
    MyDb *db = id2db[tid];
    for (int i = 0; i < row.size(); i++) {
    vector < string > items;
    my_split_csv(row[i].c_str(), items);
	insertRowFromCSV(db, items);
    }

  //db->dumpRows();
  /*
//--------------------
_byte *buf;
bool ret = db->fetchRow(2, &buf);
if(ret){
cout<<"##### "<<db->getCSVItemByIndex(3,buf)<<endl;
free(buf);
}*/
}

void preprocess()
{
    /* I am too clever; I don't need it. */
#ifdef MY_DEBUG_A
  for(int i=0;i<__idbs_all.size();i++)
	__idbs_all[i]->dumpStat();
  for(int i=0;i<__idbi_all.size();i++)
	__idbi_all[i]->dumpStat();
#endif
	
}

inline void matchKeyword(const string & kw, const char *str)
{
    if (kw.compare(str)) {
	cerr << "ERROR: syntax error " << kw << "  expected: " << str <<
	    endl;
	exit(1);
    }
}

void executeInsert(const string & sql)
{
    vector < string > token;
    my_tokenize(sql.c_str(), token);
    token.push_back(";");
    /*for(int i=0;i<token.size();i++){
      cout<<token[i]<<' ';
      }
      cout<<endl; */
    int ptr = 0;
    matchKeyword(token[ptr++], "INSERT");
    //matchKeyword(token[ptr++], "INTO");
	ptr++;
    string table_name = token[ptr++];
    //matchKeyword(token[ptr++], "VALUES");
	ptr++;
    OBJID tid = IdManager::getInstance()->getIdByName(table_name);
    MyDb *db = id2db[tid];

    while (ptr < token.size()) {
	//string csv = "";
	vector<string> csvitem;
	matchKeyword(token[ptr++], "(");
	while (token[ptr].compare(")") != 0) {
	    //csv.append( token[ptr++] );
	   if(token[ptr][0]==','){
         ptr++;
		 continue;
	   }
	   csvitem.push_back(token[ptr++]);
	}
	ptr++;			// ')'
	//insert
	//cerr<<"INSERT: "<<csv<<endl;
	insertRowFromCSV(db, csvitem);
	//cout<<csv<<endl;
	if (token[ptr].compare(";") == 0)
	    break;
	matchKeyword(token[ptr++], ",");
    }

    //cerr << "@@@DUMP AT LINE " << __LINE__ << ':' << endl;
#ifdef MY_DEBUG_A
    //db->dumpRows();
#endif

    //dumpTable (cout, iter->second);
}

struct ColBuffer {
    OBJID rowID;
    _byte *buf;
    ColBuffer(OBJID id, _byte * p):rowID(id), buf(p) {
    }
    void release() {
	if (buf){
	  //  free(buf);
	  //delete [] buf;
	  buf = NULL;
	}
	buf = NULL;
    }
};

inline bool cmpValue(const OBJID& ti1, const OBJID& ti2)
{
    return id2db[ti1]->value > id2db[ti2]->value;
}

inline bool chooseFirst(OBJID table1, OBJID table2)
{
    return !cmpValue(table1, table2);
}

#define CLIENT(x) cerr << x << " " << im->getNameById(x) << endl;


void bfsJoin(TreeNode *head, MyTable& my_tb, int total_size)
{
    IdManager* im = IdManager::getInstance();
    int ptr = 0, ret;
    RowHash rh;
    MyDb* pdb = id2db[head->t_id];
    my_tb.ids.resize(total_size, 0);
    my_tb.ids[0] = head->t_id;
    if (head->condInfo.size()) {
        pdb->filterRow(head->condInfo, rh);
        RHIter iter = rh.begin();
        while (iter != rh.end()) {
            vector < OBJID > row_vector(total_size, 0);
            row_vector[0] = *iter;
            my_tb.rows.push_back(row_vector);
            ++iter;
        }
    } else {
        SmartCursor cursor = SmartTable(pdb, pdb, 0).getCursor();
        while (ret = cursor.getNextID()) {
            vector < OBJID > row_vector(total_size, 0);            
            row_vector[0] = ret;
            my_tb.rows.push_back(row_vector);
        }
    }
    //cout << "my_tb.size(): " << my_tb.rows.size() << endl;
    vector < TreeNode* > node_queue;
    TreeNode* node = head;
    node_queue.push_back(node);
    map < OBJID, int > tid2idx;
    tid2idx[head->t_id] = 0;
    int count = 0;
    while (ptr < total_size) {
        //cout << "node_queue.size(): " << node_queue.size() << endl;
#ifdef MY_DEBUG_A 
	    cerr<<"reslut size: "<<my_tb.rows.size()<<endl;
#endif
        node = node_queue[ptr];
        pdb = id2db[node->t_id];        
        for (int i=0, size=node->child.size(); i<size; ++i) {
            node_queue.push_back(node->child[i].c_node);
            ++count;
            int op1 = node->child[i].p_c_id, op1_table = node->t_id,
                op2 = node->child[i].c_c_id, op2_table = node->child[i].c_t_id;
            my_tb.ids[count] = op2_table;
       //     CLIENT(op1);
       //     CLIENT(op2);
       //     CLIENT(op1_table);
       //     CLIENT(op2_table);
            TreeNode* child_node = node->child[i].c_node;
            for (LIter it = my_tb.rows.begin(); it != my_tb.rows.end();) {
                //cout << "iter" << (*it)[ptr] << endl;
                const _byte* buf;
                pdb->fetchRow((*it)[ptr], &buf);
                //cout << "name: " << pdb->getDbName() << endl;
                DataType type = pdb->getTypeByOBJID(op1);
                MyDb* spdb = id2db[op2_table];
                if (type == INTEGER) {
                    int x = pdb->getIntByColID(op1, buf);
                    //cerr << "x: " << x << endl;
                    IndexDbI* sdb = (IndexDbI*) spdb->getIndexDbByOBJID(op2);
					IndexDbI::Iter iter;
                    iter = sdb->data.find(x);
                    if (iter==sdb->data.end()) {
                     //   delete[] buf;
                        it = my_tb.rows.erase(it);
                        continue;
                    }
                    const _byte* sbuf;
                    bool isEmpty = true;
                    for (size_t j = 0; j < iter->second.size(); ++j) {
                        spdb->fetchRow(iter->second[j], &sbuf);
                        if (spdb->checkRow(child_node->condInfo, sbuf)) {
                            isEmpty = false;
                            if ((*it)[count] == 0) {
                                (*it)[count] = iter->second[j];
                            } else {
                                vector < OBJID > row_vector = (*it);
                                row_vector[count] = iter->second[j];
                                my_tb.rows.insert(it, row_vector);
                            }
                        }
                       // delete[] sbuf;
                    }
                    if (isEmpty) {
                       // delete[] buf;
                        it = my_tb.rows.erase(it);
                        continue;
                    }
                } else {
                    string sdata = pdb->getStringByColID(op1, buf);
                    //cout << "sdata: " << sdata << endl;
                    IndexDbS* sdb = (IndexDbS*) spdb->getIndexDbByOBJID(op2);
					IndexDbS::Iter iter;
                    iter = sdb->data.find(sdata);
                    if (iter==sdb->data.end()) {
                       // delete[] buf;
                        it = my_tb.rows.erase(it);
                        continue;
                    }
                    const _byte* sbuf;
                    bool isEmpty = true;
                    for (size_t j = 0; j < iter->second.size(); ++j) {
                        spdb->fetchRow(iter->second[j], &sbuf);
                        if (spdb->checkRow(child_node->condInfo, sbuf)) {
                            isEmpty = false;
                            if ((*it)[count] == 0) {
                                (*it)[count] = iter->second[j];
                                //cout << "add:1 " << j->second << endl;
                            } else {
                                vector < OBJID > row_vector = (*it);
                                row_vector[count] = iter->second[j];
                                my_tb.rows.insert(it, row_vector);
                                //cout << "add:2 " << j->second << endl;
                            }
                        }
                        //delete[] sbuf;
                    }
                    if (isEmpty) {
                        //delete[] buf;
                        it = my_tb.rows.erase(it);
                        continue;
                    }
                }
                //delete[] buf;
                ++it;
            }
        }
        ++ptr;
    }
}

static MyTable my_tb;


bool opCast_Fast(char *ret ){
  list<vector<OBJID> >::iterator &iter = my_tb.iter;
  if(iter==my_tb.rows.end())return false;
  IdManager *im = IdManager::getInstance(); 
  assert(iter->size()==my_tb.ids.size());
#if 0 
  ++iter;
  return true;
#endif
  const _byte *buf = 0;
  vector<const _byte *> bufs;
  for(int i=0;i<my_tb.ids.size();i++){
	  buf = 0;
      //my_tb.oc2ci[i].db->fetchRow( iter->at(i),&buf );
	  id2db[ my_tb.ids[i] ]->fetchRow( iter->at(i), &buf);
	  if(!buf){
		cerr<<"B1 "<<im->getNameById(my_tb.ids[i])<<endl;
		cerr<<"B2 "<< iter->at(i)<<endl;
	  }
	  bufs.push_back(buf );
  }
  int i = 0;
  char *ptr = ret;
  size_t len;
  for(;i<my_tb.outputCol.size();i++){
	buf = bufs[ my_tb.oc2idx[i] ];
	int idx = my_tb.oc2ci[i].idx;
	//ret.append(my_tb.oc2ci[i].db->getCSVItemByIndex(idx, buf));
	//ret.append(",");
    len = my_tb.oc2ci[i].db->getCSVItemByIndexF(idx,buf,ptr);
	ptr+=len;
	*ptr++ = ',';
  }
  if(ptr>ret){
	*(ptr-1) = 0;
  }
 
  //++my_tb.iter;
  my_tb.iter = my_tb.rows.erase(my_tb.iter);
  return true;
}

/*
int getNextAns(vector <TreeNode*>& node_queue, int total_size, vector <_byte* >& res) {
    _byte* buf;
    MyDb* pdb;
    int ret, ptr = 0;
    TreeNode* node;
    while (ptr != -1) {
        node = node_queue[ptr];
        SmartCursor& cursor = node->cursor;
      fetch_again:
        ret = cursor.getNextID();
        bool fetch_again = false;
        pdb = id2db[node->t_id];
        if (ret) {
            pdb->fetchRow(ret, &buf);
        } else {
            cursor.closeCursor();
            node_queue.pop_back();
            delete[] res[ptr];
            res[ptr] = NULL;
            --ptr;
        }
        int prev_size = count;
        for (int i=0, size=node->child.size(); i<size; ++i) {
            node_queue.push_back(node->child[i].c_node);
            ++count;
            int op1 = node->child[i].p_c_id, op1_table = node->t_id,
                op2 = node->child[i].c_c_id, op2_table = node->child[i].c_t_id;
            MyDb* spdb = id2db[op2_table];
            DataType type = pdb->getTypeByOBJID(op1);
            SmartCursor& child_cursor = node_queue[count]->cursor;
            if (type == INTEGER) {
                int x = pdb->getIntByColID(op1, buf);
                //cout << "************x: " << x << endl;
                IndexDbI* sdb = (IndexDbI*) spdb->getIndexDbByOBJID(op2);
                pair<multimap<int,OBJID >::iterator, multimap<int, OBJID>::iterator> iter;
                iter = sdb->data.equal_range(x);
                child_cursor = SmartTable(sdb, spdb, 0, true)->getCursor();
                child_cursor.data_type = INTEGER;
                multimap < int, OBJID >::iterator i = iter.first;
                if (node_queue[count]->condInfo.size()) {
                    RowHash& ht = node_queue[count]->rh;
                    for (; i != iter.second; ++i) {
                        if (ht.find(i->second) != ht.end()) {
                            break;
                        }
                    }
                }
                child_cursor.iit = i;
                child_cursor.int_end = iter.second;
                if (i == iter.second) {
                    fetch_again = true;
                }
            } else {
                string sdata = pdb->getStringByColID(op1, buf);
                //cout << "sdata: " << sdata << endl;
                IndexDbS* sdb = (IndexDbS*) spdb->getIndexDbByOBJID(op2);
                pair<multimap<string,OBJID >::iterator, multimap<string, OBJID>::iterator> iter;
                iter = sdb->data.equal_range(sdata);
                child_cursor = SmartTable(sdb, spdb, 0, true)->getCursor();
                child_cursor.data_type = VARCHAR;
                multimap < string, OBJID >::iterator i = iter.first;
                if (node_queue[count]->condInfo.size()) {
                    RowHash& ht = node_queue[count].rh;
                    for (; i != iter.second; ++i) {
                        if (ht.find(i->second) != ht.end()) {
                            break;
                        }
                    }
                }
                child_cursor.sit = i;
                child_cursor.str_end = iter.second;
                if (i == iter.second) {
                    fetch_again = true;
                }                    
            }
            if (fetch_again) {
                delete[] buf;
                while (count > prev_size) {
                    node_queue[count].closeCursor();
                    node_queue.pop_back();
                    --count;                                        
                }
                goto fetch_again;
            }
        }
        res[ptr] = buf;
        if (ptr == total_size) {
            return 1;
        } else {
            ++ptr;
        }
    }
}

static vector < TreeNode* > node_queue;
static int tsize;
*/
void execute(const string & sql0)
{
    vector < string > token, output, table, row;
    map < string, int >m;
    int i;
    string sql(sql0);
	//if(sql.length() && sql[sql.length()-1]!=';')
	//  sql.push_back(';');
#ifdef MY_DEBUG_A
    cout << "RUNNING: " << sql << endl;
#endif
    //result.clear ();


    if (sql.compare(0, 6, "INSERT") == 0) {
	executeInsert(sql);
    } else if (sql.compare(0, 6, "SELECT") == 0) {
	vector < OBJID > table;
	vector < OBJID > outputCol;
	vector < CondInfo > cond;

	parseSelect(sql, outputCol, cond, table);
	//cout<<sql<<endl;
	TreeNode *tn = createTree(table,cond,outputCol); 


    my_tb.reset();
	my_tb.outputCol = outputCol;
    bfsJoin(tn, my_tb, table.size());
    //cout << "real size: " << my_tb.rows.size() << endl;
	my_tb.conv();
	
	my_tb.iter = my_tb.rows.begin();
	//opCast( my_tb);
        //createTables(st);
        //opInitProduct(st, outputCol);

	deleteTree(tn);


    } else {
	fprintf(stderr, "Unsupported :%s\n", sql.c_str());
	exit(1);
    }
}

int next(char *row)
{
    //   return (0);
    //string strout;
	bool ret = opCast_Fast(row);
	if(!ret) return 0;
    //strout = out.back();
    //out.pop_back();
    //strcpy(row, strout.c_str());
    /*
     * This is for debug only. You should avoid unnecessary output
     * when submitting, which will hurt the performance.
     */
	//cout<<row<<endl;
    return (1);
}


CondInfo
parseCondition(const string & op1, const string & opc, const string & op2)
{
    CondInfo ci;
    //memset(&ci,0, sizeof(CondInfo));
//  ci.op1_table = colname2tc[op1].tableID;
//  ci.op2_table = colname2tableID[op2];
//  TableInfo *t1 = table_infos[ci.op1_table];
    IdManager *im = IdManager::getInstance();
    ci.op1 = im->getIdByName(op1);
    //cerr<<"########"<<op1<<" "<<t1->tableName<<endl;
    //ci.op1 = t1->name2idx[op1];
    ci.op1_table = im->getInfoById(ci.op1).parent;
    if (opc == ">") {
	ci.type = CT_GREATER;
	ci.int_op2 = std::atoi(op2.c_str());
    } else if (opc == "<") {
	ci.type = CT_LESS;
	ci.int_op2 = std::atoi(op2.c_str());
    } else if (opc == "=") {
	if (op2[0] == '\'') {	//string const
	    ci.type = CT_CONST_STRING;
	    ci.str_op2 = op2.substr(1, op2.length() - 2);
	} else if ((op2[0] >= '0' && op2[0] <= '9') || (op2[0] == '-')) {	//int const
	    ci.type = CT_CONST_INT;
	    ci.int_op2 = std::atoi(op2.c_str());
	} else {		//join
	    //cerr << "ERROR: JOIN unsupported" << endl;
	    ci.type = CT_JOIN;
	    //ci.op2_table = colname2tc[op2].tableID;
	    ci.op2 = im->getIdByName(op2);
	    ci.op2_table = im->getInfoById(ci.op2).parent;
	}
    } else {
	cerr << "ERROR: unknown condition: " << opc << endl;
    }
    return ci;
//  cout<<ci.type<<' '<<ci.str_op2<<endl;
}


void
parseSelect(const string & sql,
	    vector < OBJID > &outputCol,
	    vector < CondInfo > &cond, vector < OBJID > &table)
{
    outputCol.clear();
    cond.clear();
    table.clear();
    IdManager *im = IdManager::getInstance();
    int i;
    int ptr = 0;
    vector < string > token;
    my_tokenize(sql.c_str(), token);
    token.push_back(";");

    matchKeyword(token[ptr++], "SELECT");
    for (i = 0; i < token.size(); i++) {
	if (token[i] == "SELECT" || token[i] == ",")
	    continue;
	if (token[i] == "FROM")
	    break;
	outputCol.push_back(im->getIdByName(token[i]));
    }
    //TODO in fact, if no columns in a table after FROM are selected, we can ignore this table
    for (i++; i < token.size(); i++) {
	if (token[i] == "," || token[i] == ";")
	    continue;
	if (token[i] == "WHERE")
	    break;
	table.push_back(im->getIdByName(token[i]));
    }
	//if(token[i] ==";") break;
    for (i++; i < token.size(); i++) {
	if (token[i] == ";" || token[i] == "AND") {
	    CondInfo tmp =
		parseCondition(token[i - 3], token[i - 2], token[i - 1]);
	    cond.push_back(tmp);
	}
	if(token[i] ==";") break;
    }
}



void train(const vector < string > &query, const vector < double >&weight)
{
    // cout << "@@@@@@@@ TRAIN" << endl;
    IdManager *im = IdManager::getInstance();
//OBJID tid = im->getIdByName("item");
//id2db[tid]->createIndexDbByOBJID(im->getIdByName("i_id"));
//id2db[tid]->createIndexDbByOBJID(im->getIdByName("i_price"));
//id2db[tid]->createIndexDbByOBJID(im->getIdByName("i_name"));

    for (int i = 0; i < query.size(); i++) {
	// string  sql(query[i]);
	  const string &sql = query[i];
	//cerr<<sql<<endl;
	if (sql.compare(0, 6, "SELECT") != 0)
	    continue;
	vector < OBJID > table;
	vector < OBJID > outputCol;
	vector < CondInfo > cond;

	parseSelect(sql, outputCol, cond, table);

	for (int i = 0; i < cond.size(); i++) {
	    switch (cond[i].type) {
	    case CT_CONST_STRING:
	    case CT_GREATER:
	    case CT_LESS:
	    case CT_CONST_INT:
		id2db[cond[i].op1_table]->createIndexDbByOBJID(cond[i].
							       op1);
		break;
	    case CT_JOIN:	//join
		id2db[cond[i].op1_table]->createIndexDbByOBJID(cond[i].
							       op1);
		id2db[cond[i].op2_table]->createIndexDbByOBJID(cond[i].
							       op2);
                break;
	    default:
                // cerr << "sql: " << sql << endl;
		cerr << "TRAIN WARNING: unimplemented cond type:" <<
		    cond[i].type;
	    }
	}
    }
}

void close()
{
    /* I have nothing to do. */
}
